/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.module.client;

import org.pajamas.starter.core.util.RegisterAsPajamasClient;
import org.pajamas.starter.integration.spring.bean.BeanConstants;
import org.pajamas.starter.integration.spring.cloud.GlobalCircuitBreakerConfigBuilderCustomizer;
import org.pajamas.starter.integration.spring.cloud.GlobalTimeLimiterConfigBuilderCustomizer;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.cloud.circuitbreaker.resilience4j.ReactiveResilience4JCircuitBreakerFactory;
import org.springframework.cloud.circuitbreaker.resilience4j.Resilience4JConfigBuilder;
import org.springframework.cloud.client.circuitbreaker.Customizer;
import org.springframework.context.annotation.Bean;
import org.springframework.core.annotation.Order;

import java.time.Duration;

import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.core.IntervalFunction;
import io.github.resilience4j.timelimiter.TimeLimiterConfig;

import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_FAILURE_RATE_THRESHOLD;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_MINIMUM_NUMBER_OF_CALLS;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLIDING_WINDOW_SIZE;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLOW_CALL_DURATION_THRESHOLD;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_SLOW_CALL_RATE_THRESHOLD;
import static io.github.resilience4j.circuitbreaker.CircuitBreakerConfig.DEFAULT_WRITABLE_STACK_TRACE_ENABLED;

/**
 * @author william
 * @since 2024/3/31
 */
@ConditionalOnProperty(prefix = "org.pajamas.client.gateway", name = "enabled", havingValue = "true")
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@RegisterAsPajamasClient.ConfigurationBean
public class PajamasGatewayClientConfiguration {
    @Order(BeanConstants.ORDER_LATEST_SECOND)
    @Bean
    public GlobalCircuitBreakerConfigBuilderCustomizer defaultCircuitBreakerConfigBuilderCustomizer() {
        return builder ->
                builder
                        .slidingWindowType(CircuitBreakerConfig.SlidingWindowType.TIME_BASED)
                        .slidingWindowSize(DEFAULT_SLIDING_WINDOW_SIZE)
                        .minimumNumberOfCalls(DEFAULT_MINIMUM_NUMBER_OF_CALLS)
                        .waitDurationInOpenState(Duration.ofSeconds(DEFAULT_SLOW_CALL_DURATION_THRESHOLD))
                        .waitIntervalFunctionInOpenState(IntervalFunction
                                .of(Duration.ofSeconds(DEFAULT_SLOW_CALL_DURATION_THRESHOLD)))
                        .enableAutomaticTransitionFromOpenToHalfOpen()
                        .permittedNumberOfCallsInHalfOpenState(DEFAULT_PERMITTED_CALLS_IN_HALF_OPEN_STATE)
                        .writableStackTraceEnabled(DEFAULT_WRITABLE_STACK_TRACE_ENABLED)
                        .slowCallRateThreshold(DEFAULT_SLOW_CALL_RATE_THRESHOLD)
                        .slowCallDurationThreshold(Duration.ofSeconds(DEFAULT_SLOW_CALL_DURATION_THRESHOLD))
                        .failureRateThreshold(DEFAULT_FAILURE_RATE_THRESHOLD)
                        .recordExceptions(Throwable.class)
                        .ignoreExceptions();
    }

    @Order(BeanConstants.ORDER_LATEST_SECOND)
    @Bean
    public GlobalTimeLimiterConfigBuilderCustomizer defaultTimeLimiterConfigBuilderCustomizer() {
        return builder ->
                builder
                        .timeoutDuration(Duration.ofSeconds(1))
                        .cancelRunningFuture(true);
    }

    @ConditionalOnProperty(prefix = "org.pajamas.client.gateway", value = "circuitBreaker", havingValue =
            "true", matchIfMissing = true)
    @Bean
    public Customizer<ReactiveResilience4JCircuitBreakerFactory> pajamasGatewayGlobalCircuitBreakerFactory(
            ObjectProvider<GlobalCircuitBreakerConfigBuilderCustomizer> globalCircuitBreakerConfigBuilderCustomizers,
            ObjectProvider<GlobalTimeLimiterConfigBuilderCustomizer> globalTimeLimiterConfigBuilderCustomizers) {

        CircuitBreakerConfig.Builder circuitBreakerConfigBuilder = CircuitBreakerConfig.custom();
        globalCircuitBreakerConfigBuilderCustomizers
                .orderedStream()
                .forEach(customizer -> customizer.customize(circuitBreakerConfigBuilder));

        TimeLimiterConfig.Builder timeLimiterConfigBuilder = TimeLimiterConfig.custom();
        globalTimeLimiterConfigBuilderCustomizers
                .orderedStream()
                .forEach(customizer -> customizer.customize(timeLimiterConfigBuilder));

        return factory -> factory.configureDefault(id -> new Resilience4JConfigBuilder(id)
                .circuitBreakerConfig(circuitBreakerConfigBuilder.build())
                .timeLimiterConfig(timeLimiterConfigBuilder.build())
                .build());
    }
}
